# -- coding: utf-8 --
from mip import Model, INTEGER, CONTINUOUS, BINARY, minimize, xsum, LinExpr


# 铁路线路
class RailwayLine:
    def __init__(self, list):
        self.StationList = []
        self.SectionList = []
        self.PrevSection = {}
        self.NextSection = {}
        for name in list:
            if len(self.StationList) == 0:
                self.StationList.append(name)
            else:
                last = self.StationList[len(self.StationList) - 1]
                station = name
                section = Section(last, station)
                self.StationList.append(station)
                self.SectionList.append(section)
                self.PrevSection[station] = section
                self.NextSection[last] = section

    # 打印
    def Print(self):
        for station in self.StationList:
            if station == self.StationList[len(self.StationList) - 1]:
                print(station)
            else:
                print(station, '-->', end="")

    # 通过车站名称获取区间
    def GetSec(self, sta1, sta2):
        for sec in self.SectionList:
            if sec.start_station == sta1 and sec.end_station == sta2:
                return sec

    # 获取除去首末站的其它车站
    def GetInterStation(self):
        for station in self.StationList:
            if not (station == self.StationList[0] or station == self.StationList[len(self.StationList) - 1]):
                yield station


# 区间
class Section:
    def __init__(self, station1, station2):
        self.start_station = station1
        self.end_station = station2


# 备选集
class AlternativeSet:
    def __init__(self):
        self.LineList = []


# 方案线
class TrainLine:
    def __init__(self, name):
        self.Name = name
        self.AlongList = []
        self.AlongSection = []
        self.CoveredOD = []

    # 增加停站
    def AddStop(self, station, isStop):
        self.AlongList.append(Stop(station, isStop))
        # 获取区间运行时间

    # 增加索引
    def BuildAlongSection(self, net):
        for index in range(len(self.AlongList) - 1):
            self.AlongSection.append(net.GetSec(self.AlongList[index].Station, self.AlongList[index + 1].Station))

        for index1 in range(len(self.AlongList)):
            if self.AlongList[index1].Stop:
                sta1 = self.AlongList[index1].Station
                for index2 in range(index1+1 ,len(self.AlongList)):
                    if self.AlongList[index2].Stop:
                        self.CoveredOD.append(Section(sta1, self.AlongList[index2].Station))

    def Passed(self, section):
        return self.AlongSection.count(section)

    def Covered(self, section):
        for od in self.CoveredOD:
            if od.start_station == section.start_station and od.end_station == section.end_station:
                return True
        return False


# 列车停站
class Stop:
    def __init__(self, station, stop):
        self.Station = station
        self.Stop = stop

    def IsPass(self):
        return self.Stop


# 参数类
class LineParameters:
    def __init__(self, capacity, cost, demand):
        self.Section_Capacity = capacity  # 区间运行时间
        self.Cost = cost
        self.Demand = demand


# 生成小案例
def glb_generate_test_world():
    net = RailwayLine(['A', 'B', 'C', 'D', 'E', 'F'])  # 线路上的停站

    alterSet = AlternativeSet()
    line1 = TrainLine('Line1')
    line1.AddStop('A', True)
    line1.AddStop('B', False)
    line1.AddStop('C', True)
    line1.AddStop('D', False)
    line1.AddStop('E', True)
    line1.BuildAlongSection(net)

    line2 = TrainLine('Line2')
    line2.AddStop('A', True)
    line2.AddStop('B', True)
    line2.AddStop('C', True)
    line2.AddStop('D', True)
    line2.AddStop('E', True)
    line2.BuildAlongSection(net)

    line3 = TrainLine('Line3')
    line3.AddStop('A', True)
    line3.AddStop('B', False)
    line3.AddStop('C', False)
    line3.AddStop('D', False)
    line3.AddStop('E', True)
    line3.BuildAlongSection(net)

    alterSet.LineList.append(line1)
    alterSet.LineList.append(line2)
    alterSet.LineList.append(line3)

    capacity = {net.GetSec('A', 'B'): 3,  # 区间能力约束
                net.GetSec('B', 'C'): 5,
                net.GetSec('C', 'D'): 7,
                net.GetSec('D', 'E'): 3,
                net.GetSec('E', 'F'): 4}

    demand = {Section('A', 'D'): 2,  # 服务频率约束
              Section('A', 'C'): 1}

    params = LineParameters(capacity, 100, demand)
    return net, alterSet, params


# 求解问题
def glb_solve_mip(net, alterSet, params):
    model = Model()
    frequency_var = {}

    # 初始化变量
    for line in alterSet.LineList:
        frequency_var[line] = model.add_var(lb=0, ub=999, var_type=INTEGER, name=line.Name)

    # 约束1：区间能力约束
    for sec in net.SectionList:  # 遍历所有的区间
        model += xsum(line.Passed(sec) * frequency_var[line] for line in alterSet.LineList) <= params.Section_Capacity[sec]

    # 约束2：OD服务频率约束
    for od in params.Demand:
        model += xsum(line.Covered(od) * frequency_var[line] for line in alterSet.LineList) >= params.Demand[od]

    # 目标：成本最小
    model.objective = minimize(params.Fixed_Cost * xsum(frequency_var[line] for line in alterSet.LineList))

    # 求解
    model.write("model.lp")
    model.max_mip_gap = 0.01
    model.optimize(max_seconds=300)

    # 输出
    if model.num_solutions:
        for line in alterSet.LineList:
            print('{0}:{1}'.format(line.Name, frequency_var[line].x))
        print('目标函数值:', format(model.objective_value))


# Press the green button in the gutter to run the script.
if __name__ == '__main__':
    net, alterSet, params = glb_generate_test_world()  # 线路上的停站
    net.Print()
    glb_solve_mip(net, alterSet, params)